Object oriented refactoring of fcType#577
Conversation
…e and construction
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #577 +/- ##
==========================================
+ Coverage 69.44% 69.47% +0.02%
==========================================
Files 181 183 +2
Lines 34072 34208 +136
Branches 5930 5932 +2
==========================================
+ Hits 23662 23765 +103
- Misses 10273 10306 +33
Partials 137 137 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| // SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. | ||
| // SPDX-License-Identifier: BSD-3-Clause |
There was a problem hiding this comment.
In this PR, the functions fft and ifft have been moved from this file to fourier_interpolation.cpp, inside the new FourierInterpolation class. I think this makes sense, since those functions were tightly coupled to fcType.
However, this file also contains another function, igbc, which is now the only function left. The function is not related to Fourier transform, so it seems odd to keep the name of this file as it is.
I propose renaming it to igbc.cpp (and the header to igbc.h).
There was a problem hiding this comment.
@michelebucelli You might could put igbc in utils.h,cpp or all_fun.h,cpp depending on I suppose if it is fun or not.
| // @todo[michelebucelli] This is consistent with the old code, but is | ||
| // incorrect when use_ramp = true. In that case, the derivative should be | ||
| // zero outside the interpolation interval [initial_time, initial_time + | ||
| // period]. | ||
| if (evaluate_derivative) | ||
| derivative[i] = linear_trend_slopes[i]; |
There was a problem hiding this comment.
This was probably an unintended behavior of the old code.
When using the "ramp" option, the following function is computed (the implementation is formulated slightly different):
For times outside of the inerpolation interval
| // @todo[michelebucelli] This is consistent with the old code, but is | |
| // incorrect when use_ramp = true. In that case, the derivative should be | |
| // zero outside the interpolation interval [initial_time, initial_time + | |
| // period]. | |
| if (evaluate_derivative) | |
| derivative[i] = linear_trend_slopes[i]; | |
| if (evaluate_derivative) { | |
| if (use_ramp && (time < initial_time || time > initial_time + period)) { | |
| derivative[i] = 0.0; | |
| } else { | |
| derivative[i] = linear_trend_slopes[i]; | |
| } | |
| } |
I tried this out and all the tests still pass, so I propose changing this behavior.
| // @todo[michelebucelli] This seems pretty fragile! It happens in a few | ||
| // other places in this file as well, where using an increment | ||
| // operator (*= or +=) changes the order of operations resulting in | ||
| // changes in the test values. This should be investigated and the | ||
| // best (i.e. most accurate) operation order should be chosen. | ||
| value[j] = value[j] + fourier_coefficients_real(j, i) * std::cos(K) - | ||
| fourier_coefficients_imaginary(j, i) * std::sin(K); |
There was a problem hiding this comment.
This took me a while to figure out 😅 There's at least another place where the same thing happens. I'm honestly not sure which is the most correct option, but the fact that the order of operations makes a measurable difference probably means that this needs to be carefully evaluated.
There was a problem hiding this comment.
@michelebucelli Very interesting !
What is the measurable difference that you are seeing ?
| std::tie(value, derivative) = gt.value_and_derivative(x_start - 1.0); | ||
| ASSERT_NEAR(value[0], temporal_values[0][1], 1e-6) | ||
| << "Expected value before initial time to be equal to initial value of " | ||
| "linear trend"; | ||
|
|
||
| // Check that after the final time the value is equal to the final value of | ||
| // the linear trend. | ||
| std::tie(value, derivative) = gt.value_and_derivative(x_end + 1.0); | ||
| ASSERT_NEAR(value[0], temporal_values.back()[1], 1e-6) | ||
| << "Expected value after final time to be equal to final value of linear " | ||
| "trend"; | ||
|
|
||
| // Check that the value in the interpolation interval is a linear | ||
| // interpolation of the initial and final value. | ||
| for (double t = x_start; t <= x_end; t += 0.5) { | ||
| std::tie(value, derivative) = gt.value_and_derivative(t); | ||
|
|
||
| const double expected_y = | ||
| temporal_values[0][1] + | ||
| (temporal_values.back()[1] - temporal_values[0][1]) * (t - x_start) / | ||
| (x_end - x_start); | ||
| const double expected_z = | ||
| temporal_values[0][2] + | ||
| (temporal_values.back()[2] - temporal_values[0][2]) * (t - x_start) / | ||
| (x_end - x_start); | ||
|
|
||
| ASSERT_NEAR(value[0], expected_y, 1e-6) | ||
| << "Expected value in interpolation interval to be linear " | ||
| "interpolation of initial and final value"; | ||
| ASSERT_NEAR(value[1], expected_z, 1e-6) | ||
| << "Expected value in interpolation interval to be linear " | ||
| "interpolation of initial and final value"; |
There was a problem hiding this comment.
These assertions should be extended to include checks on the evaluated derivative, once we agree whether to change or keep the current behavior as per my previous comment.
ktbolt
left a comment
There was a problem hiding this comment.
@michelebucelli Nice work !
We name files containing classes with their class name (I think).
| // SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. | ||
| // SPDX-License-Identifier: BSD-3-Clause |
There was a problem hiding this comment.
@michelebucelli You might could put igbc in utils.h,cpp or all_fun.h,cpp depending on I suppose if it is fun or not.
| for (unsigned int j = 0; j < n_components; ++j) { | ||
| result.linear_trend_initial_values[j] = values(j, 0); | ||
| result.linear_trend_slopes[j] = | ||
| (values(j, n_time_points - 1) - values(j, 0)) / result.period; |
There was a problem hiding this comment.
@michelebucelli Should you check if result.period is zero ?
|
|
||
| for (unsigned int j = 0; j < result.n_components; j++) { | ||
| const double s = | ||
| (values(j, i + 1) - values(j, i)) / (times[i + 1] - times[i]); |
There was a problem hiding this comment.
@michelebucelli Why all of these const ? Maybe a (good?) habit to guard against changing these variables later I guess.
|
|
||
| for (unsigned int i = 0; i < n_time_points - 1; ++i) { | ||
| const double ko = 2.0 * consts::pi * tmp * times[i] / result.period; | ||
| const double kn = 2.0 * consts::pi * tmp * times[i + 1] / result.period; |
There was a problem hiding this comment.
@michelebucelli Why are we using consts::pi here rather than std::numbers::pi ?
| FourierInterpolation | ||
| FourierInterpolation::from_time_series_file(const std::string &file_name, | ||
| const unsigned int n_components, | ||
| const bool use_ramp) { |
There was a problem hiding this comment.
@michelebucelli Fundamental types are typically not declared const.
| "At least 1 Fourier coefficient is required to set up Fourier " | ||
| "interpolation. 0 Fourier coefficients were provided."); | ||
| } | ||
|
|
There was a problem hiding this comment.
@michelebucelli n_time_points could be zero for an incorrect format file format: first value is non-numeric.
And n_fourier_coefficients could be zero if the first value is not an int or there is a format error, say a comma is used to separate values.
| bool initialized = defined(); | ||
| cm.bcast(cm_mod, &initialized); | ||
|
|
||
| if (initialized) { |
There was a problem hiding this comment.
@michelebucelli It is not very clear what it means for an object to be initialized. Does that mean it has data ?
| // @todo[michelebucelli] This seems pretty fragile! It happens in a few | ||
| // other places in this file as well, where using an increment | ||
| // operator (*= or +=) changes the order of operations resulting in | ||
| // changes in the test values. This should be investigated and the | ||
| // best (i.e. most accurate) operation order should be chosen. | ||
| value[j] = value[j] + fourier_coefficients_real(j, i) * std::cos(K) - | ||
| fourier_coefficients_imaginary(j, i) * std::sin(K); |
There was a problem hiding this comment.
@michelebucelli Very interesting !
What is the measurable difference that you are seeing ?
Fixes #575.
Current situation
The class
fcTypeis currently defined inComMod.h, and its interface and that of related function make its use slightly more cumbersome and less seamless than it needs to be (see discussion in #575 for more details).Release Notes
fcTyperenamed toFourierInterpolation, and its data members (now private) renamed with more explicit names.FourierInterpolationimplementation moved to dedicated filesfourier_interpolation.{h,cpp}.FourierInterpolationobject is now done through static methodsFourierInterpolation::from_time_seriesandFourierInterpolation::from_fourier_coefficients(while before it was done "manually" everywhere this was needed).FourierInterpolation::from_time_series_fileandFourierInterpolation::from_fourier_coefficients_filemanage reading data from file (while before it was done "manually" everywhere this was needed).FourierInterpolation::distribute.FourierInterpolation::valueandFourierInterpolation::value_and_derivative.fftandifftwere tightly related tofcType(they worked on anfcTypeinstance), and they have been merged into the methodsFourierInterpolationclass (for construction and evaluation, respectively).All this should make it a bit easier to add support for reading time-dependent data from file in new classes (which is part of the aims of #519).
Documentation
The class and its methods have been given detailed Doxygen documentation.
Testing
The unit test and the integration tests all pass with the new interface.
TODO before marking as ready
Code of Conduct & Contributing Guidelines